import { getGlobalZIndex } from '../utils.js'

export const popover_default_config = {
    classNames: ['vc-popover-core'], // 盒子class
    styles: {}, // 盒子样式
    placement: 'bottom', // 盒子位置
    zIndex: null, // 盒子图层排序值
    offset: 4, // 盒子偏移的值
    onMount: () => { }, // 完成后的回调
    onDestroy: () => { }, // 销毁后的回调
    toBody: true,
    content: '',
    $from: '', // Selector
    delay: 100,
    trigger: 'click'
}

export default class PopoverCore {
    static dataPopoverIdName = 'data-popover-id'
    index = 0
    // 触发按钮
    $reference = null
    // 盒子
    $box = null
    options = {}
    timer = null
    constructor(reference, options) {
        this.$reference = reference

        const opts = {
            ...popover_default_config,
        }

        const {
            classNames,
            styles,
            placement,
            zIndex,
            offset,
            toBody,
            onMount,
            onDestroy,
            content,
            from,
            delay,
            trigger
        } = options

        if (Array.isArray(classNames)) {
            opts.classNames.push(...classNames.filter(v => typeof v === 'string'))
        }
        if (styles) {
            opts.styles = { ...opts.styles, ...styles }
        }
        if (placement) {
            opts.placement = placement
        }
        if (zIndex) {
            opts.zIndex = zIndex
        } else {
            opts.zIndex = getGlobalZIndex()
        }
        if (offset) {
            opts.offset = offset
        }
        if (offset) {
            opts.offset = offset
        }
        if (onMount) {
            opts.onMount = onMount
        }
        if (offset) {
            opts.onDestroy = onDestroy
        }
        if (typeof toBody === 'boolean') {
            opts.toBody = toBody
        }
        if (content) {
            opts.content = content
        }
        if (from) {
            opts.$from = from
        }
        if (delay) {
            opts.delay = delay
        }
        if (trigger) {
            opts.trigger = trigger
        }

        this.options = opts
        this.popoverId = opts.zIndex
        this.onMount = onMount
        this.onDestroy = onDestroy
        // console.log('options', this.options)

        this.create()
        this.mount()
        this.update()
        this.setTriggerAttr(PopoverCore.dataPopoverIdName, this.options.zIndex)
        this.bind()
    }
    getChild() {
        const { content, $from } = this.options
        // 这里不可使用克隆 child 元素，因为会丢失已绑定事件
        let child = $from
            ? typeof $from === 'string'
                ? document.querySelector($from)
                : $from
            : ''
        if (!child) {
            child = document.createElement('div')
            child.innerHTML = content || ''
        }
        child.removeAttribute('hidden')
        return child
    }
    getParent() {
        const { classNames } = this.options
        const el = document.createElement('div')
        el.classList.add.apply(el.classList, classNames)
        const child = this.getChild()
        el.appendChild(child)
        return el
    }
    create() {
        const { styles: _styles, zIndex } = this.options
        const el = this.getParent()
        // const triggerRect = this.$reference.getBoundingClientRect()
        const styles = {
            // top: triggerRect.bottom + 'px',
            // left: triggerRect.left + 'px',
            'z-index': zIndex,
            ..._styles
        }
        const cssText = Object.keys(styles).reduce(function (prev, propName) {
            prev.push([propName, styles[propName]].join(':'))
            return prev
        }, []).join(';')

        // 该 cssText 只能设置一次
        el.style.cssText = cssText
        el.setAttribute(PopoverCore.dataPopoverIdName, zIndex)
        this.$box = el
    }

    getPlacement(triggerRect, boxRect) {
        const { offset, placement } = this.options
        const innerWidth = window.innerWidth
        const innerHeight = window.innerHeight
        const width = boxRect.width
        const height = boxRect.height - offset
        const rc = []
        if (triggerRect.top > height) {
            rc.push('top')
        }
        if ((innerHeight - triggerRect.bottom) > height) {
            rc.push('bottom')
        }
        if (triggerRect.left > width) {
            rc.push('left')
        }
        if ((innerWidth - triggerRect.right) > width) {
            rc.push('right')
        }
        if (rc.some(v => v === placement)) {
            return placement
        }
        return rc[0]
    }

    update() {
        const { offset } = this.options
        const triggerRect = this.$reference.getBoundingClientRect()
        const boxRect = this.$box.getBoundingClientRect()
        const placement = this.getPlacement(triggerRect, boxRect)
        const innerWidth = window.innerWidth
        // const innerHeight = window.innerHeight
        const width = boxRect.width
        // const height = boxRect.height - offset
        switch (placement) {
            case 'top':
                this.setBoxStyle(
                    'top',
                    `${triggerRect.top - boxRect.height - offset}px`
                )
                if (innerWidth - triggerRect.left > width) {
                    this.setBoxStyle('left', `${triggerRect.left}px`)
                } else {
                    this.setBoxStyle('right', '0px')
                }
                break
            case 'bottom':
                this.setBoxStyle(
                    'top',
                    `${triggerRect.bottom + offset}px`
                )
                if (innerWidth - triggerRect.left > width) {
                    this.setBoxStyle('left', `${triggerRect.left}px`)
                } else {
                    this.setBoxStyle('right', '0px')
                }
                break
            case 'left':
                this.setBoxStyle(
                    'top',
                    `${triggerRect.bottom - triggerRect.height / 2 - boxRect.height / 2}px`
                )
                this.setBoxStyle(
                    'left',
                    `${triggerRect.left - boxRect.width - offset}px`
                )
                break
            case 'right':
                this.setBoxStyle(
                    'top',
                    `${triggerRect.bottom - triggerRect.height / 2 - boxRect.height / 2}px`
                )
                this.setBoxStyle(
                    'left',
                    `${triggerRect.right + offset}px`
                )
                break
            default:
                break
        }
    }

    setTriggerAttr(name, value) {
        this.$reference.setAttribute(name, value)
    }
    isShow() {
        return this.$box.style.display === 'block'
    }
    show() {
        console.log('timer', this.timer)
        if (this.timer) {
            clearTimeout(this.timer)
            this.timer = null
        }
        this.setBoxStyle('display', 'block')
        this.update()
    }

    hide() {
        this.timer = setTimeout(() => {
            this.setBoxStyle('display', 'none')
        }, this.options.delay)
    }

    setBoxStyle(name, value) {
        this.$box.style[name] = value
    }

    mount() {
        const { toBody } = this.options
        if (toBody) {
            document.body.appendChild(this.$box)
        } else {
            this.$reference.appendChild(this.$box)
        }
        if (typeof this.onMount === 'function') {
            this.onMount()
        }
    }

    bind() {
        const { trigger } = this.options
        if (trigger === 'click') {
            document.addEventListener('click', this.isHide.bind(this))
            window.addEventListener('scroll', this.scrollHide.bind(this))
        } else {
            this.$box.addEventListener('mouseenter', () => {
                this.show()
            })
            this.$box.addEventListener('mouseleave', () => {
                this.hide()
            })
        }
    }

    isHide(e) {
        if (this.$reference?.contains(e.target) || this.$box?.contains(e.target)) {
            return
        }
        this.hide()
    }

    scrollHide() {
        this.hide()
    }

    destroy() {
        document.removeEventListener('click', this.isHide.bind(this))
        window.removeEventListener('scroll', this.scrollHide.bind(this))
        this.$box && this.$box.remove()
        if (typeof this.onDestroy === 'function') {
            this.onDestroy()
        }
    }
}